using System;
using System.Collections;
using Deepcode.Flux.Core.Extensions.Codons;

namespace Deepcode.Flux.Core.Extensions
{
	/// <summary>
	/// Represents a single node in the addin tree. A node can be a codon and path, or it can be
	/// a path only, depending on whether the codon field is set to anything or not.
	/// </summary>
	public class AddinTreeNode
	{
		/// <summary>
		/// Hashtable to store the childnodes, keyed by ID
		/// </summary>
		private Hashtable childNodes = new Hashtable();
		/// <summary>
		/// Association with the codon object that this node represents.
		/// </summary>
		private ICodon codon = null;
		
		public Hashtable Children
		{
			get
			{
				return childNodes;
			}
		}
		
		public ICodon Codon
		{
			get
			{
				return codon;
			}
			set
			{
				codon = value; 
			}
		}
		#region Sorting child nodes
		/// <summary>
		/// Gets a simple array of the child nodes sorted according to their insertbefore/after settings
		/// </summary>
		/// <returns></returns>
		private AddinTreeNode [] GetSubnodesArray()
		{
			AddinTreeNode node = this;
			int index = node.Children.Count;
			AddinTreeNode[] sortedNodes = new AddinTreeNode[index];
			Hashtable  visited   = new Hashtable(index);
			Hashtable  ancestor = new Hashtable(index);
			
			foreach(string key in node.Children.Keys) 
			{
				visited[key] = false;
				ancestor[key] = new ArrayList();
			}
			
			foreach(DictionaryEntry child in node.Children)
			{
				if(((AddinTreeNode)child.Value).Codon.InsertAfter != null)
				{
					for(int i = 0; i < ((AddinTreeNode)child.Value).Codon.InsertAfter.Length; ++i)
					{
						if(ancestor.Contains(((AddinTreeNode)child.Value).Codon.InsertAfter[i].ToString()))
						{
							((ArrayList)ancestor[((AddinTreeNode)child.Value).Codon.InsertAfter[i].ToString()]).Add(child.Key);
						}
					}
				}
				
				if(((AddinTreeNode)child.Value).Codon.InsertBefore != null)
				{
					for(int i = 0; i < ((AddinTreeNode)child.Value).Codon.InsertBefore.Length; ++i)
					{
						if(ancestor.Contains(child.Key))
						{
							((ArrayList)ancestor[child.Key]).Add(((AddinTreeNode)child.Value).Codon.InsertBefore[i]);
						}
					}
				}
			}
			
			string[] keyarray = new string[visited.Keys.Count];
			visited.Keys.CopyTo(keyarray, 0);
			
			for (int i = 0; i < keyarray.Length; ++i) 
			{
				if((bool)visited[keyarray[i]] == false)
				{
					index = Visit(keyarray[i], node.Children, sortedNodes, 
						visited, ancestor, index);
				}
			}
			return sortedNodes;
		}
		private int Visit(string key, Hashtable nodes, AddinTreeNode[] sortedNodes, Hashtable visited, Hashtable ancestor, int index)
		{
			visited[key] = true;
			foreach (string anch in (ArrayList)ancestor[key]) 
			{
				if ((bool)visited[anch] == false) 
				{
					index = Visit(anch, nodes, sortedNodes, visited, ancestor, index);
				}
			}
			
			sortedNodes[--index] = (AddinTreeNode)nodes[key];
			return index;
		}

		#endregion
		/// <summary>
		/// Asks one codon node to build the real object that it represents
		/// </summary>
		/// <param name="childItemID"></param>
		/// <param name="caller"></param>
		/// <returns></returns>
		public object BuildChildItem(string childItemID, object caller)
		{
			AddinTreeNode childItem = (AddinTreeNode) Children[childItemID];
			if( childItem == null ) return null;
			if( childItem.Codon == null ) return null;
			// Build any children if necessary
			ArrayList subitems = childItem.BuildChildItems( caller );
			return childItem.Codon.BuildItem( caller, subitems );
		}
		/// <summary>
		/// Builds all of the real objects within this node
		/// </summary>
		/// <param name="caller"></param>
		/// <returns></returns>
		public ArrayList BuildChildItems(object caller)
		{
			ArrayList items = new ArrayList();
			foreach( AddinTreeNode currentNode in this.GetSubnodesArray() )
			{
				if( currentNode.Codon != null )
				{
					// Build any child items
					ArrayList subItems = currentNode.BuildChildItems( caller );
					// Build this item itself.
					object newItem = currentNode.Codon.BuildItem( caller, subItems );
					// Add it to the list if it was created
					if( newItem != null )
						items.Add( newItem );
				}
			}
			return items;
		}
	}
}
